/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "js_date.h"
#include <ctime>
#include <regex>
#include <sys/time.h>
#include "base/builtins_base.h"

namespace ark::ecmascript {
using NumberHelper = base::NumberHelper;
void DateUtils::TransferTimeToDate(int64_t timeMs, std::array<int64_t, DATE_LENGTH> *date)
{
    (*date)[HOUR] = Mod(timeMs, MS_PER_DAY);                                 // ms from hour, minutes, second, ms
    (*date)[DAYS] = (timeMs - (*date)[HOUR]) / MS_PER_DAY;                   // days from year, month, day
    (*date)[MS] = (*date)[HOUR] % MS_PER_SECOND;                             // ms
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[MS]) / MS_PER_SECOND;           // s from hour, minutes, second
    (*date)[SEC] = (*date)[HOUR] % SEC_PER_MINUTE;                           // second
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[SEC]) / SEC_PER_MINUTE;         // min from hour, minutes
    (*date)[MIN] = (*date)[HOUR] % SEC_PER_MINUTE;                           // min
    (*date)[HOUR] = ((*date)[HOUR] - (*date)[MIN]) / SEC_PER_MINUTE;         // hour
    (*date)[WEEKDAY] = Mod(((*date)[DAYS] + LEAP_NUMBER[0]), DAY_PER_WEEK);  // weekday
    (*date)[YEAR] = GetYearFromDays(&((*date)[DAYS]));                       // year
}
// static
bool DateUtils::IsLeap(int64_t year)
{
    return year % LEAP_NUMBER[0] == 0 && (year % LEAP_NUMBER[1] != 0 || year % LEAP_NUMBER[2] == 0);  // 2: means index
}

// static
int64_t DateUtils::GetDaysInYear(int64_t year)
{
    int64_t number;
    number = IsLeap(year) ? (DAYS_IN_YEAR + 1) : DAYS_IN_YEAR;
    return number;
}

// static
int64_t DateUtils::GetDaysFromYear(int64_t year)
{
    return DAYS_IN_YEAR * (year - YEAR_NUMBER[0]) + FloorDiv(year - YEAR_NUMBER[1], LEAP_NUMBER[0]) -
           FloorDiv(year - YEAR_NUMBER[2], LEAP_NUMBER[1]) +  // 2: year index
           FloorDiv(year - YEAR_NUMBER[3], LEAP_NUMBER[2]);   // 3, 2: year index
}

// static
int64_t DateUtils::FloorDiv(int64_t a, int64_t b)
{
    ASSERT(b != 0);
    int64_t m = a % b;
    int64_t res = m < 0 ? ((a - m - b) / b) : ((a - m) / b);
    return res;
}

// static
int64_t DateUtils::GetYearFromDays(int64_t *days)
{
    int64_t realDay;
    int64_t dayTemp = 0;
    int64_t d = *days;
    int64_t year = FloorDiv(d * APPROXIMATION_NUMBER[0], APPROXIMATION_NUMBER[1]) + YEAR_NUMBER[0];
    realDay = d - GetDaysFromYear(year);
    while (realDay != 0) {
        if (realDay < 0) {
            year--;
        } else {
            dayTemp = GetDaysInYear(year);
            if (realDay < dayTemp) {
                break;
            }
            year++;
        }
        realDay = d - GetDaysFromYear(year);
    }
    *days = realDay;
    return year;
}

// static
int64_t DateUtils::Mod(int64_t a, int b)
{
    ASSERT(b != 0);
    int64_t m = a % b;
    int64_t res = m < 0 ? (m + b) : m;
    return res;
}

// static
// 20.4.1.11
double JSDate::MakeTime(double hour, double min, double sec, double ms)
{
    if (std::isfinite(hour) && std::isfinite(min) && std::isfinite(sec) && std::isfinite(ms)) {
        double hourInteger = NumberHelper::TruncateDouble(hour);
        double minInteger = NumberHelper::TruncateDouble(min);
        double secInteger = NumberHelper::TruncateDouble(sec);
        double msInteger = NumberHelper::TruncateDouble(ms);
        return hourInteger * MS_PER_HOUR + minInteger * MS_PER_MINUTE + secInteger * MS_PER_SECOND + msInteger;
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.12
double JSDate::MakeDay(double year, double month, double date)
{
    if (std::isfinite(year) && std::isfinite(month) && std::isfinite(date)) {
        double yearInteger = NumberHelper::TruncateDouble(year);
        double monthInteger = NumberHelper::TruncateDouble(month);
        int64_t y = static_cast<int64_t>(yearInteger) + static_cast<int64_t>(monthInteger / MOUTH_PER_YEAR);
        int64_t m = static_cast<int64_t>(monthInteger) % MOUTH_PER_YEAR;
        if (m < 0) {
            m += MOUTH_PER_YEAR;
            y -= 1;
        }

        int64_t days = DateUtils::GetDaysFromYear(y);
        int index = DateUtils::IsLeap(year) ? 1 : 0;
        days += DAYS_FROM_MONTH[index][m];
        return static_cast<double>(days - 1) + NumberHelper::TruncateDouble(date);
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.13
double JSDate::MakeDate(double day, double time)
{
    if (std::isfinite(day) && std::isfinite(time)) {
        return time + day * MS_PER_DAY;
    }
    return base::NAN_VALUE;
}

// static
// 20.4.1.14
double JSDate::TimeClip(double time)
{
    if (-MAX_TIME_IN_MS <= time && time <= MAX_TIME_IN_MS) {
        return NumberHelper::TruncateDouble(time);
    }
    return base::NAN_VALUE;
}

// 20.4.1.8
double JSDate::LocalTime(double timeMs) const
{
    return timeMs + GetLocalOffsetFromOS(timeMs, true);
}

// 20.4.1.9
double JSDate::UTCTime(double timeMs) const
{
    return timeMs - GetLocalOffsetFromOS(timeMs, false);
}

// static
int JSDate::GetSignedNumFromString(const PandaString &str, int len, int *index)
{
    int res = 0;
    GetNumFromString(str, len, index, &res);
    if (str.at(0) == NEG) {
        return -res;
    }
    return res;
}

// static
bool JSDate::GetNumFromString(const PandaString &str, int len, int *index, int *num)
{
    int indexStr = *index;
    char oneByte = 0;
    while (indexStr < len) {
        oneByte = str.at(indexStr);
        if (oneByte >= '0' && oneByte <= '9') {
            break;
        }
        indexStr++;
    }
    if (indexStr >= len) {
        return false;
    }
    int value = 0;
    while (indexStr < len) {
        oneByte = str.at(indexStr);
        int val = oneByte - '0';
        if (val >= 0 && val <= NUM_NINE) {
            value = value * TEN + val;
            indexStr++;
        } else {
            break;
        }
    }
    *num = value;
    *index = indexStr;
    return true;
}

// 20.4.1.7
int64_t JSDate::GetLocalOffsetInMin(const JSThread *thread, int64_t timeMs, bool isLocal)
{
    if (!isLocal) {
        return 0;
    }
    double localOffset = this->GetLocalOffset().GetDouble();
    if (localOffset == MAX_DOUBLE) {
        localOffset = static_cast<double>(GetLocalOffsetFromOS(timeMs, isLocal));
        SetLocalOffset(thread, JSTaggedValue(localOffset));
    }
    return localOffset;
}

// static
JSTaggedValue JSDate::LocalParseStringToMs(const PandaString &str)
{
    int year = 0;
    int month = 0;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = str.length();
    bool isLocal = false;
    PandaString::size_type indexGmt;
    PandaString::size_type indexPlus = PandaString::npos;
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    int localTime = 0;
    int localHours = 0;
    int localMinutes = 0;
    int64_t localMs = 0;
    PandaString::size_type localSpace;
    localSpace = str.find(' ', index);
    PandaString strMonth = str.substr(localSpace + 1, LENGTH_MONTH_NAME);
    for (int i = 0; i < MOUTH_PER_YEAR; i++) {
        if (strMonth == monthName[i]) {
            month = i;
            break;
        }
    }
    index += (LENGTH_MONTH_NAME + 1);
    GetNumFromString(str, len, &index, &date);
    GetNumFromString(str, len, &index, &year);
    indexGmt = str.find("GMT", index);
    if (indexGmt == PandaString::npos) {
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        GetNumFromString(str, len, &index, &seconds);
        isLocal = true;
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    } else {
        indexPlus = str.find(PLUS, indexGmt);
        int indexLocal = static_cast<int>(indexGmt);
        GetNumFromString(str, indexGmt, &index, &hours);
        GetNumFromString(str, indexGmt, &index, &minutes);
        GetNumFromString(str, indexGmt, &index, &seconds);
        GetNumFromString(str, len, &indexLocal, &localTime);
        localHours = localTime / HUNDRED;
        localMinutes = localTime % HUNDRED;
        localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
        if (indexPlus != PandaString::npos) {
            localMs = -localMs;
        }
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}

// static
JSTaggedValue JSDate::UtcParseStringToMs(const PandaString &str)
{
    int year = 0;
    int month = 0;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = str.length();
    PandaString::size_type indexGmt;
    PandaString::size_type indexPlus = PandaString::npos;
    int localTime = 0;
    int localHours = 0;
    int localMinutes = 0;
    int64_t localMs = 0;
    bool isLocal = false;
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    GetNumFromString(str, len, &index, &date);
    PandaString strMonth = str.substr(index + 1, LENGTH_MONTH_NAME);
    for (int i = 0; i < MOUTH_PER_YEAR; i++) {
        if (strMonth == monthName[i]) {
            month = i;
            break;
        }
    }
    index += (LENGTH_MONTH_NAME + 1);
    GetNumFromString(str, len, &index, &year);
    indexGmt = str.find("GMT", index);
    if (indexGmt == PandaString::npos) {
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        GetNumFromString(str, len, &index, &seconds);
        isLocal = true;
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    } else {
        indexPlus = str.find(PLUS, indexGmt);
        int indexLocal = static_cast<int>(indexGmt);
        GetNumFromString(str, indexGmt, &index, &hours);
        GetNumFromString(str, indexGmt, &index, &minutes);
        GetNumFromString(str, indexGmt, &index, &seconds);
        GetNumFromString(str, len, &indexLocal, &localTime);
        localHours = localTime / HUNDRED;
        localMinutes = localTime % HUNDRED;
        localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
        if (indexPlus != PandaString::npos) {
            localMs = -localMs;
        }
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (isLocal && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}
// static
JSTaggedValue JSDate::IsoParseStringToMs(const PandaString &str)
{
    char flag = 0;
    int year;
    int month = 1;
    int date = 1;
    int hours = 0;
    int minutes = 0;
    int seconds = 0;
    int ms = 0;
    int index = 0;
    int len = str.length();
    year = GetSignedNumFromString(str, len, &index);
    PandaString::size_type indexT = str.find(FLAG_TIME, index);
    PandaString::size_type indexZ = str.find(FLAG_UTC, index);
    PandaString::size_type indexEndFlag = 0;
    int64_t localMs = 0;
    if (indexZ != PandaString::npos) {
        indexEndFlag = indexZ;
    } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == NEG) {
        indexEndFlag = len - INDEX_PLUS_NEG;
        flag = NEG;
    } else if (len >= MIN_LENGTH && str.at(len - INDEX_PLUS_NEG) == PLUS) {
        indexEndFlag = len - INDEX_PLUS_NEG;
        flag = PLUS;
    }
    if (indexT != PandaString::npos) {
        if ((indexT - index) == LENGTH_PER_TIME) {
            GetNumFromString(str, len, &index, &month);
        } else if ((indexT - index) == (LENGTH_PER_TIME + LENGTH_PER_TIME)) {
            GetNumFromString(str, len, &index, &month);
            GetNumFromString(str, len, &index, &date);
        }
        GetNumFromString(str, len, &index, &hours);
        GetNumFromString(str, len, &index, &minutes);
        if (indexEndFlag > 0) {
            if (indexEndFlag - index == LENGTH_PER_TIME) {
                GetNumFromString(str, len, &index, &seconds);
            } else if (indexEndFlag - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
                GetNumFromString(str, len, &index, &seconds);
                GetNumFromString(str, len, &index, &ms);
            }
        } else {
            if (len - index == LENGTH_PER_TIME) {
                GetNumFromString(str, len, &index, &seconds);
            } else if (len - index == (LENGTH_PER_TIME + LENGTH_PER_TIME + 1)) {
                GetNumFromString(str, len, &index, &seconds);
                GetNumFromString(str, len, &index, &ms);
            }
        }
    } else {
        GetNumFromString(str, len, &index, &month);
        GetNumFromString(str, len, &index, &date);
    }
    if (indexEndFlag > 0) {
        int localHours = 0;
        int localMinutes = 0;
        if (indexZ == PandaString::npos) {
            GetNumFromString(str, len, &index, &localHours);
            GetNumFromString(str, len, &index, &localMinutes);
            if (flag == PLUS) {
                localMs = static_cast<int64_t>(-MakeTime(localHours, localMinutes, 0, 0));
            } else {
                localMs = static_cast<int64_t>(MakeTime(localHours, localMinutes, 0, 0));
            }
        }
    }
    if (indexEndFlag == 0 && indexT != PandaString::npos) {
        localMs -= (GetLocalOffsetFromOS(localMs, true) * MS_PER_MINUTE);
    }

    double day = MakeDay(year, month - 1, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    double timeValue = TimeClip(MakeDate(day, time));
    if (std::isnan(timeValue)) {
        return JSTaggedValue(timeValue);
    }
    if (flag == 0 && timeValue < CHINA_1901_MS && (-localMs / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
        timeValue += static_cast<double>(localMs - CHINA_BEFORE_1901_MS);
    } else {
        timeValue += localMs;
    }
    return JSTaggedValue(timeValue);
}

// 20.4.3.2 static
JSTaggedValue JSDate::Parse(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    const PandaString isoPriStr = "(^|(\\+|-)(\\d{2}))";
    const PandaString isoDateStr =
        "(((\\d{4})-(0?[1-9]|1[0-2])-(0?[1-9]|1[0-9]|2[0-9]|3[0-1]))"
        "|((\\d{4})-(0?[1-9]|1[0-2]))|(\\d{4}))";
    const PandaString isoTimeStr =
        "((T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])"
        "\\.(\\d{3}))|(T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))|"
        "(T([01][0-9]|2[0-3]):([0-5][0-9]))|(T([01][0-9]|2[0-3])))"
        "($|Z|((\\+|-)(([01][0-9]|2[0-3]):([0-5][0-9]))))";
    const PandaString isoRegStr = isoPriStr + isoDateStr + "($|Z|(" + isoTimeStr + "))";
    const PandaString utcDateStr =
        "^\\D*(0?[1-9]|1[0-9]|2[0-9]|3[0-1]) "
        "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\\d{4})";
    const PandaString timeStr =
        "(( ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))|( ([01][0-9]|2[0-3]):([0-5][0-9])))"
        "($| *| GMT *| GMT((\\+|-)(\\d{4})) *)";
    const PandaString utcRegStr = utcDateStr + "($| *| GMT *| GMT((\\+|-)(\\d{4})) *|(" + timeStr + "))";
    const PandaString localDateStr =
        "^[a-zA-Z]* (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) "
        "(0?[1-9]|1[0-9]|2[0-9]|3[0-1]) (\\d{4})";
    const PandaString localRegStr = localDateStr + "($| *| GMT *| GMT((\\+|-)(\\d{4})) *|(" + timeStr + "))";

    std::regex isoReg(isoRegStr);
    std::regex utcReg(utcRegStr);
    std::regex localReg(localRegStr);
    JSHandle<JSTaggedValue> msg = builtins_common::GetCallArg(argv, 0);
    JSHandle<JSTaggedValue> str = JSHandle<JSTaggedValue>::Cast(JSTaggedValue::ToString(thread, msg));
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    PandaString date = ConvertToPandaString(EcmaString::Cast(str->GetTaggedObject()));
    if (std::regex_match(date, isoReg)) {
        return IsoParseStringToMs(date);
    }
    if (std::regex_match(date, utcReg)) {
        return UtcParseStringToMs(date);
    }
    if (std::regex_match(date, localReg)) {
        return LocalParseStringToMs(date);
    }
    return JSTaggedValue(base::NAN_VALUE);
}

// 20.4.3.1
JSTaggedValue JSDate::Now()
{
    // time from now is in ms.
    int64_t ans;
    struct timeval tv {};
    gettimeofday(&tv, nullptr);
    ans = static_cast<int64_t>(tv.tv_sec) * MS_PER_SECOND + (tv.tv_usec / MS_PER_SECOND);
    return JSTaggedValue(static_cast<double>(ans));
}

// 20.4.4.2 static
JSTaggedValue JSDate::UTC(EcmaRuntimeCallInfo *argv)
{
    double year;
    double month = 0;
    double date = 1;
    double hours = 0;
    double minutes = 0;
    double seconds = 0;
    double ms = 0;
    JSThread *thread = argv->GetThread();
    JSHandle<JSTaggedValue> yearArg = builtins_common::GetCallArg(argv, 0);
    JSTaggedNumber yearValue = JSTaggedValue::ToNumber(thread, yearArg);
    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
    if (yearValue.IsNumber()) {
        year = yearValue.GetNumber();
        if (std::isfinite(year) && !yearValue.IsInt()) {
            year = NumberHelper::TruncateDouble(year);
        }
        if (year >= 0 && year <= (HUNDRED - 1)) {
            year = year + NINETEEN_HUNDRED_YEAR;
        }
    } else {
        year = base::NAN_VALUE;
    }
    uint32_t index = 1;
    uint32_t numArgs = argv->GetArgsNumber();
    JSTaggedValue res;
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        month = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        date = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        hours = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        minutes = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        seconds = res.GetNumber();
        index++;
    }
    if (numArgs > index) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, index);
        res = JSTaggedValue::ToNumber(thread, value);
        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
        ms = res.GetNumber();
    }
    double day = MakeDay(year, month, date);
    double time = MakeTime(hours, minutes, seconds, ms);
    return JSTaggedValue(TimeClip(MakeDate(day, time)));
}

int64_t JSDate::GetLocalOffsetFromOS(int64_t timeMs, bool isLocal)
{
    // Preserve the old behavior for non-ICU implementation by ignoring both timeMs and is_utc.
    if (!isLocal) {
        return 0;
    }
    timeMs /= JSDate::THOUSAND;
    time_t tv = std::time(reinterpret_cast<time_t *>(&timeMs));
    struct tm tm {};
    // localtime_r is only suitable for linux.
    struct tm *t = localtime_r(&tv, &tm);
    // tm_gmtoff includes any daylight savings offset.
    return t->tm_gmtoff / SEC_PER_MINUTE;
}

// 20.4.4.10
JSTaggedValue JSDate::GetTime() const
{
    return GetTimeValue();
}

// static
PandaString JSDate::StrToTargetLength(const PandaString &str, int length)
{
    int len;
    if (str[0] == NEG) {
        len = static_cast<int>(str.length() - 1);
    } else {
        len = static_cast<int>(str.length());
    }
    int dif = length - len;
    PandaString sub;
    for (int i = 0; i < dif; i++) {
        sub += '0';
    }
    if (str[0] == NEG) {
        sub = NEG + sub + str.substr(1, len);
    } else {
        sub = sub + str;
    }
    return sub;
}

bool JSDate::GetThisDateValues(std::array<int64_t, DATE_LENGTH> *date, bool isLocal) const
{
    double timeMs = this->GetTimeValue().GetDouble();
    if (std::isnan(timeMs)) {
        return false;
    }
    GetDateValues(timeMs, date, isLocal);
    return true;
}

// 20.4.4.35
JSTaggedValue JSDate::ToDateString(JSThread *thread) const
{
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    std::array<PandaString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString year = StrToTargetLength(ToPandaString(fields[YEAR]), STR_LENGTH_YEAR);
    PandaString day = StrToTargetLength(ToPandaString(fields[DAYS]), STR_LENGTH_OTHERS);
    PandaString str = weekdayName[fields[WEEKDAY]] + SPACE + monthName[fields[MONTH]] + SPACE + day + SPACE + year;
    JSHandle<EcmaString> result = thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str);
    return result.GetTaggedValue();
}

// static
PandaString JSDate::ToDateString(double timeMs)
{
    if (std::isnan(timeMs)) {
        return "Invalid Date";
    }
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    std::array<PandaString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<int64_t, DATE_LENGTH> fields = {0};
    GetDateValues(timeMs, &fields, true);
    PandaString localTime;
    int localMin = 0;
    localMin = GetLocalOffsetFromOS(localMin, true);
    if (timeMs < CHINA_BEFORE_1900_MS && localMin == CHINA_AFTER_1901_MIN) {
        localMin = CHINA_BEFORE_1901_MIN;
    }
    if (localMin >= 0) {
        localTime += PLUS;
    } else if (localMin < 0) {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToPandaString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToPandaString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    PandaString year = ToPandaString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    PandaString weekday = weekdayName[fields[WEEKDAY]];
    PandaString month = monthName[fields[MONTH]];
    PandaString day = StrToTargetLength(ToPandaString(fields[DAYS]), STR_LENGTH_OTHERS);
    PandaString hour = StrToTargetLength(ToPandaString(fields[HOUR]), STR_LENGTH_OTHERS);
    PandaString minute = StrToTargetLength(ToPandaString(fields[MIN]), STR_LENGTH_OTHERS);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
                      second + SPACE + "GMT" + localTime;
    return str;
}
// 20.4.4.36
JSTaggedValue JSDate::ToISOString(JSThread *thread) const
{
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, false)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString year = ToPandaString(fields[YEAR]);
    if (year[0] == NEG) {
        year = StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
    } else if (year.length() > STR_LENGTH_YEAR) {
        year = PLUS + StrToTargetLength(year, STR_LENGTH_YEAR + STR_LENGTH_OTHERS);
    } else {
        year = StrToTargetLength(year, STR_LENGTH_YEAR);
    }
    PandaString month = StrToTargetLength(ToPandaString(fields[MONTH] + 1), STR_LENGTH_OTHERS);
    PandaString day = StrToTargetLength(ToPandaString(fields[DAYS]), STR_LENGTH_OTHERS);
    PandaString hour = StrToTargetLength(ToPandaString(fields[HOUR]), STR_LENGTH_OTHERS);
    PandaString minute = StrToTargetLength(ToPandaString(fields[MIN]), STR_LENGTH_OTHERS);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString ms = StrToTargetLength(ToPandaString(fields[MS]), STR_LENGTH_OTHERS + 1);
    PandaString str =
        year + NEG + month + NEG + day + FLAG_TIME + hour + COLON + minute + COLON + second + POINT + ms + FLAG_UTC;
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

PandaString JSDate::GetLocaleTimeStr(const std::array<int64_t, DATE_LENGTH> &fields) const
{
    PandaString hour;
    if (fields[HOUR] > MOUTH_PER_YEAR) {
        hour = ToPandaString(fields[HOUR] - MOUTH_PER_YEAR);
    } else {
        hour = ToPandaString(fields[HOUR]);
    }
    PandaString minute = ToPandaString(fields[MIN]);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString str = hour + COLON + minute + COLON + second;
    if (fields[HOUR] >= MOUTH_PER_YEAR) {
        str = "下午" + str;
    } else {
        str = "上午" + str;
    }
    return str;
}

PandaString JSDate::GetLocaleDateStr(const std::array<int64_t, DATE_LENGTH> &fields) const
{
    PandaString year;
    if (fields[YEAR] < 0) {
        year = ToPandaString(-fields[YEAR] + 1);
    } else {
        year = ToPandaString(fields[YEAR]);
    }
    PandaString month = ToPandaString(fields[MONTH] + 1);
    PandaString day = ToPandaString(fields[DAYS]);
    PandaString str = year + VIRGULE + month + VIRGULE + day;
    return str;
}

// 20.4.4.38
JSTaggedValue JSDate::ToLocaleDateString(JSThread *thread) const
{
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString str = GetLocaleDateStr(fields);
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

// 20.4.4.39
JSTaggedValue JSDate::ToLocaleString(JSThread *thread) const
{
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString strDate = GetLocaleDateStr(fields);
    PandaString strTime = GetLocaleTimeStr(fields);
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(strDate + SPACE + strTime).GetTaggedValue();
}

// 20.4.4.40
JSTaggedValue JSDate::ToLocaleTimeString(JSThread *thread) const
{
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString str = GetLocaleTimeStr(fields);
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

// 20.4.4.41
JSTaggedValue JSDate::ToString(JSThread *thread) const
{
    std::array<PandaString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    int localMin = 0;
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString localTime;
    localMin = GetLocalOffsetFromOS(localMin, true);
    if (static_cast<int64_t>(this->GetTimeValue().GetDouble()) < CHINA_BEFORE_1900_MS &&
        localMin == CHINA_AFTER_1901_MIN) {
        localMin = CHINA_BEFORE_1901_MIN;
    }
    if (localMin >= 0) {
        localTime += PLUS;
    } else if (localMin < 0) {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToPandaString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToPandaString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    PandaString year = ToPandaString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    PandaString weekday = weekdayName[fields[WEEKDAY]];
    PandaString month = monthName[fields[MONTH]];
    PandaString day = StrToTargetLength(ToPandaString(fields[DAYS]), STR_LENGTH_OTHERS);
    PandaString hour = StrToTargetLength(ToPandaString(fields[HOUR]), STR_LENGTH_OTHERS);
    PandaString minute = StrToTargetLength(ToPandaString(fields[MIN]), STR_LENGTH_OTHERS);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString str = weekday + SPACE + month + SPACE + day + SPACE + year + SPACE + hour + COLON + minute + COLON +
                      second + SPACE + "GMT" + localTime;
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

// 20.4.4.42
JSTaggedValue JSDate::ToTimeString(JSThread *thread) const
{
    int localMin = 0;
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, true)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString localTime;
    localMin = GetLocalOffsetFromOS(localMin, true);
    if (static_cast<int64_t>(this->GetTimeValue().GetDouble()) < CHINA_BEFORE_1900_MS &&
        localMin == CHINA_AFTER_1901_MIN) {
        localMin = CHINA_BEFORE_1901_MIN;
    }
    if (localMin >= 0) {
        localTime += PLUS;
    } else {
        localTime += NEG;
        localMin = -localMin;
    }
    localTime = localTime + StrToTargetLength(ToPandaString(localMin / MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    localTime = localTime + StrToTargetLength(ToPandaString(localMin % MINUTE_PER_HOUR), STR_LENGTH_OTHERS);
    PandaString hour = StrToTargetLength(ToPandaString(fields[HOUR]), STR_LENGTH_OTHERS);
    PandaString minute = StrToTargetLength(ToPandaString(fields[MIN]), STR_LENGTH_OTHERS);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString str = hour + COLON + minute + COLON + second + SPACE + "GMT" + localTime;
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

// 20.4.4.43
JSTaggedValue JSDate::ToUTCString(JSThread *thread) const
{
    std::array<PandaString, DAY_PER_WEEK> weekdayName = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    std::array<PandaString, MOUTH_PER_YEAR> monthName = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                                         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    std::array<int64_t, DATE_LENGTH> fields = {0};
    if (!GetThisDateValues(&fields, false)) {
        return JSTaggedValue(base::NAN_VALUE);
    }
    PandaString year = ToPandaString(fields[YEAR]);
    year = StrToTargetLength(year, STR_LENGTH_YEAR);
    PandaString weekday = weekdayName[fields[WEEKDAY]];
    PandaString month = monthName[fields[MONTH]];
    PandaString day = StrToTargetLength(ToPandaString(fields[DAYS]), STR_LENGTH_OTHERS);
    PandaString hour = StrToTargetLength(ToPandaString(fields[HOUR]), STR_LENGTH_OTHERS);
    PandaString minute = StrToTargetLength(ToPandaString(fields[MIN]), STR_LENGTH_OTHERS);
    PandaString second = StrToTargetLength(ToPandaString(fields[SEC]), STR_LENGTH_OTHERS);
    PandaString ms = StrToTargetLength(ToPandaString(fields[MS]), STR_LENGTH_OTHERS);
    PandaString str = weekday + COMMA + SPACE + day + SPACE + month + SPACE + year + SPACE + hour + COLON + minute +
                      COLON + second + SPACE + "GMT";
    return thread->GetEcmaVM()->GetFactory()->NewFromCanBeCompressString(str).GetTaggedValue();
}

// 20.4.4.44
JSTaggedValue JSDate::ValueOf() const
{
    return this->GetTimeValue();
}

// static
void JSDate::GetDateValues(double timeMs, std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
{
    int64_t tz = 0;
    int64_t timeMsInt;
    int month = 0;
    timeMsInt = static_cast<int64_t>(timeMs);
    if (isLocal) {  // timezone offset
        tz = GetLocalOffsetFromOS(timeMsInt, isLocal);
        if (timeMsInt < CHINA_BEFORE_1900_MS && tz == CHINA_AFTER_1901_MIN) {
            timeMsInt += CHINA_BEFORE_1901_ADDMS;
            timeMsInt += tz * MS_PER_SECOND * SEC_PER_MINUTE;
            tz = CHINA_BEFORE_1901_MIN;
        } else {
            timeMsInt += tz * MS_PER_SECOND * SEC_PER_MINUTE;
        }
    }

    DateUtils::TransferTimeToDate(timeMsInt, date);

    int index = DateUtils::IsLeap((*date)[YEAR]) ? 1 : 0;
    int left = 0;
    int right = MONTH_PER_YEAR;
    while (left <= right) {
        int middle = (left + right) / 2;  // 2 : half
        if (DAYS_FROM_MONTH[index][middle] <= (*date)[DAYS] && DAYS_FROM_MONTH[index][middle + 1] > (*date)[DAYS]) {
            month = middle;
            (*date)[DAYS] -= DAYS_FROM_MONTH[index][month];
            break;
        }
        if ((*date)[DAYS] > DAYS_FROM_MONTH[index][middle]) {
            left = middle + 1;
        } else if ((*date)[DAYS] < DAYS_FROM_MONTH[index][middle]) {
            right = middle - 1;
        }
    }

    (*date)[MONTH] = month;
    (*date)[DAYS] = (*date)[DAYS] + 1;
    (*date)[TIMEZONE] = -tz;
}

double JSDate::GetDateValue(double timeMs, uint8_t code, bool isLocal) const
{
    if (std::isnan(timeMs)) {
        return base::NAN_VALUE;
    }
    std::array<int64_t, DATE_LENGTH> date = {0};
    GetDateValues(timeMs, &date, isLocal);
    return static_cast<double>(date[code]);
}

JSTaggedValue JSDate::SetDateValue(EcmaRuntimeCallInfo *argv, uint32_t code, bool isLocal) const
{
    // get date values.
    std::array<int64_t, DATE_LENGTH> date = {0};
    double timeMs = this->GetTimeValue().GetDouble();
    if (std::isnan(timeMs)) {
        return JSTaggedValue(base::NAN_VALUE);
    }

    // get values from argv.
    uint32_t argc = argv->GetArgsNumber();
    if (argc == 0) {
        return JSTaggedValue(base::NAN_VALUE);
    }

    uint32_t firstValue = code & CODE_FLAG;
    uint32_t endValue = (code >> CODE_4_BIT) & CODE_FLAG;
    uint32_t count = endValue - firstValue;

    if (argc < count) {
        count = argc;
    }

    GetDateValues(timeMs, &date, isLocal);

    [[maybe_unused]] EcmaHandleScope handleScope(argv->GetThread());
    for (uint32_t i = 0; i < count; i++) {
        JSHandle<JSTaggedValue> value = builtins_common::GetCallArg(argv, i);
        JSThread *thread = argv->GetThread();
        JSTaggedNumber res = JSTaggedValue::ToNumber(thread, value);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        double temp = res.GetNumber();
        if (std::isnan(temp)) {
            return JSTaggedValue(base::NAN_VALUE);
        }
        date[firstValue + i] = NumberHelper::TruncateDouble(temp);
    }
    // set date values.
    return JSTaggedValue(SetDateValues(&date, isLocal));
}

// static
double JSDate::SetDateValues(const std::array<int64_t, DATE_LENGTH> *date, bool isLocal)
{
    int64_t month = DateUtils::Mod((*date)[MONTH], MONTH_PER_YEAR);
    int64_t year = (*date)[YEAR] + ((*date)[MONTH] - month) / MONTH_PER_YEAR;
    int64_t days = DateUtils::GetDaysFromYear(year);
    int index = DateUtils::IsLeap(year) ? 1 : 0;
    days += DAYS_FROM_MONTH[index][month];

    days += (*date)[DAYS] - 1;
    int64_t millisecond =
        (((*date)[HOUR] * MIN_PER_HOUR + (*date)[MIN]) * SEC_PER_MINUTE + (*date)[SEC]) * MS_PER_SECOND + (*date)[MS];
    int64_t result = days * MS_PER_DAY + millisecond;
    if (isLocal) {
        int64_t offset = GetLocalOffsetFromOS(result, isLocal) * SEC_PER_MINUTE * MS_PER_SECOND;
        if (result < CHINA_1901_MS && (offset / MS_PER_MINUTE) == CHINA_AFTER_1901_MIN) {
            offset += CHINA_BEFORE_1901_ADDMS;
        }
        result -= offset;
    }
    return TimeClip(result);
}
}  // namespace ark::ecmascript
